package com.smart.events.touch {
	
	import flash.display.InteractiveObject;
	import flash.events.MouseEvent;
	import flash.geom.Point;
	import flash.utils.Timer; 
	
	// todo: often times on a flick, will get press, drag, then flick....	
	public class MoveEventHandler  {
		       
		// flick is before, drag is after this time 
		static private var FLICK_TIMEOUT:int = 200; 
		static private var PRESS_TIMER_INTERVAL:int = 10; 
		static private var PRESS_MIN_TIME:int = 200; 

		// distance 		
		static private var FLICK_MIN_MOVEMENT:int = 15; 
		static private var DRAG_MIN_MOVEMENT:int = 5; 
		
		// drag 	 
		private var _isDrag:Boolean; 
		
		// flick 
		private var _flickTimer:Timer;
		private var _flickDir:Number; 
		private var _flickDist:Number; 

		// press 
		private var _dispatchedPress:Boolean = false; 
		private var _pressPoint:Point; 	
				
		// what is our interactive object 
		private var _target:InteractiveObject;
		
		// our press point 
		private var _stageX:int;
		private var _stageY:int;
		
		private var _localX:int; 
		private var _localY:int; 
		

		// debug mode 		
		private var _debugMode : Boolean = false; 
					
		public function MoveEventHandler() {

			debug("MoveEventHandler: init");
									 
			// our flick minimum time 
			_flickTimer = new Timer(FLICK_TIMEOUT,1); 			
		}
		
		// set minimum flicking requirement 
		public function set minFlickMovement ( num:int ) : void {
			FLICK_MIN_MOVEMENT = num; 
		}
		
		// set minimum dragging movement requirement 
		public function set minDragMovement ( num:int ) : void {
			DRAG_MIN_MOVEMENT = num; 	
		}
		
		// register events to this object 
		public function register ( _tar:InteractiveObject ) : void {
			init(_tar); 
		}
		
		// unregister from what we were listening from 
		public function unregister () : void {
			dispose(); 		
		}

		// add our initial listener 
		private function init ( _tar:InteractiveObject ):void {				
			_target = _tar; 			
			_target.addEventListener(MouseEvent.MOUSE_DOWN,mouseDownHandler);
		}
		
		// get rid of all listeners 
		private function dispose() : void {
			_target.removeEventListener(MouseEvent.MOUSE_DOWN,mouseDownHandler);
			_target.stage.removeEventListener(MouseEvent.MOUSE_UP,mouseUpHandler); 
			_target.removeEventListener(MouseEvent.MOUSE_OUT,mouseOutHandler); 		
			_target.removeEventListener(MouseEvent.MOUSE_MOVE,mouseMoveHandler);			
		}
					
		private function mouseDownHandler(event:MouseEvent):void {

			debug("MoveEventHanlder: DOWN handler called"); 

			// new events to listen to 
			_target.stage.addEventListener(MouseEvent.MOUSE_UP, mouseUpHandler);			
			_target.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler);
			
			// events to remove 
			_target.removeEventListener(MouseEvent.MOUSE_DOWN,mouseDownHandler); 

			// we are starting out events 
			start_move(event); 			
			
		}
		
		// only used on mouse down 
		private function start_move (event:MouseEvent) : void {

			// dispatch pressed 
			dispatch(MoveEvent.MOVE_PRESS);
									
			// where we pressed  
			_pressPoint = new Point(event.stageX,event.stageY); 	
			_dispatchedPress = false; 
			
			// we just started pressing, not a drag yet  		
			_isDrag = false; 
												
			// update where 
			_stageX = event.stageX;
			_stageY = event.stageY;			

			// from local
			_localX = event.localX; 
			_localY = event.localY; 

		}
		
		private function mouseUpHandler(event:MouseEvent):void {
						
			debug("MoveEventManager: MOUSE UP " + event.target ); 

			// listen to down again 
			_target.addEventListener(MouseEvent.MOUSE_DOWN,mouseDownHandler); 
		
			// remove these events 
			_target.removeEventListener(MouseEvent.MOUSE_MOVE,mouseMoveHandler); 
			_target.stage.removeEventListener(MouseEvent.MOUSE_UP,mouseUpHandler); 			

			// must meet minimum time requirement for flick  
			if ( _flickTimer.running ) {

				// dispatch
				dispatch(MoveEvent.MOVE_FLICK); 
			} 
					
			// release should only occur if we actually released on top of the object 
			if ( _target.hitTestPoint(event.stageX,event.stageY) )  {						
					
				dispatch(MoveEvent.MOVE_RELEASE);

				// update where touch occured 
				_stageX = event.stageX;
				_stageY = event.stageY;	
				
				_localX = event.localX; 
				_localY = event.localY; 
						

			}
			
			reset_for_next(); 
		}
		
		
		private function mouseMoveHandler(event:MouseEvent):void {		

			debug("MoveEventManager: mouse move " + _target ); 
		
			// update points 
			_stageX = event.stageX;
			_stageY = event.stageY;			

			_localX = event.localX; 
			_localY = event.localY; 
						
			// save current point 		
			var _curMovePoint:Point = new Point (event.stageX,event.stageY); 
			
			// flick handling 			
			var _flick_distance:Number = Point.distance(_curMovePoint,_pressPoint) 
						
			// if we moved enough, we want to start determine if we release
			// within flick's tolerance 
			if ( _flick_distance > FLICK_MIN_MOVEMENT ) {
				
				// this is checked at mouse up
				_flickTimer.reset(); 
				_flickTimer.start(); 
				_flickDir = calc_degree(_curMovePoint,_pressPoint); 
				_flickDist = _flick_distance; 
				
				// if we dispatched a flick, no longer want to dispatch drag 
				_isDrag = false; 
			}			

			// check our current distance 			
			var _drag_distance:Number = Point.distance(_curMovePoint,_pressPoint);				

			// dragging handler 
			// dont check when flick timer is running 
			if ( ( _drag_distance > DRAG_MIN_MOVEMENT ) && ( _flickTimer.running == false ) ) {
				_isDrag = true; 
			}
				
			// we are dragging, dispatch event 
			if ( _isDrag ) {
				dispatch(MoveEvent.MOVE_DRAG); 
			}
												
			// update our press point
			// so we can have..drag...drag...drag..flick!  
			_pressPoint = new Point (_curMovePoint.x,_curMovePoint.y); 
				
		}
		
		// reset whatever you need for the next mouse down event 
		private function reset_for_next () : void {
			
			_flickTimer.stop();  		
			_dispatchedPress = false; 			
			_isDrag = false; 
		}
		
		// mouse is out of interactive object
		private function mouseOutHandler ( e:MouseEvent ) : void {

			debug("MoveEventHanlder: OUT called " + e.target ); 
			
			// ignore if out is not this object 
			if ( e.target != _target ) {
				debug("MoveEventHandler: not same target ignore ");
				return; 	
			}			
		
			// remove mouse out
			// we are not removing stage events since that will always be triggerd 
			_target.removeEventListener(MouseEvent.MOUSE_OUT,mouseOutHandler); 

			// send out event 
			dispatch(MoveEvent.MOVE_OUT); 
			
		}
					
		// how to dispatch the event 
		private function dispatch(type:String):void {
			debug("MoveEventHandler: dispatch " + type);
			_target.dispatchEvent(new MoveEvent(type,this));
		}
		
		// accessor methods /////////////////
		
		public function get stageX():int {
			return _stageX;
		}
		
		public function get stageY():int {
			return _stageY;
		}
		
		public function get localX () : int {	
			return _localX; 
		}
		
		public function get localY () : int {
			return _localY; 
		}
		
		public function get flickDir():int {
			return int(_flickDir);
		}
		
		public function get flickDistance () : int {
			return int(_flickDist);	
		}		
		
		// some math equations /////////// 
		
		
		private function calc_degree ( start_point:Point, end_point:Point ) : Number {
					
			var diff_x:Number = end_point.x - start_point.x; 
			var diff_y:Number = end_point.y - start_point.y; 
			
			return tangent_angle(diff_x,diff_y); 
		}
		
		private function tangent_angle ( x_value:Number, y_value:Number ) : Number {
			
			var radians:Number = Math.atan2(y_value,x_value); 
			
			// handle going all the way around
			if (radians < 0)
				radians += 6.283185
		
			// quick conversion of radions to degrees
			return radians * 57.29578;
						
		}
		
		// for debugging ///// 
		private function debug ( str:String ) : void {			
			if ( _debugMode ) trace(str); 
		}
	}
}